/*
 * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package top.continew.admin.hanfu.core.oss;

import cn.hutool.core.io.FileUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import top.continew.admin.hanfu.cache.redis.FileStatusRedis;
import top.continew.admin.hanfu.core.oss.domain.FileStatus;
import top.continew.admin.hanfu.domain.OssFile;
import top.continew.admin.hanfu.service.impl.OssFileServiceImpl;

import java.io.*;
import java.math.BigInteger;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
 *
 * @apiNote oss系统
 * @author bbm
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class OSS {

    @Value("${oss.url}")
    private String url;

    private final String clientUrl = "/oss/prod/";

    private final OssFileServiceImpl ossFileService;

    private final OssFactory ossFactory;

    private final FileStatusRedis fileStatusRedis;

    /**
     * 保存文件
     *
     */
    public List<OssFile> saveFile(String Subject, String user, MultipartFile... file) {

        List<OssFile> ossFiles = new ArrayList<>();

        //        /home/hanfu/file路径
        //        /home/hanfu/file/
        String path = user + "/" + Subject + "/"; // 路径地址1/hanfuImg/
        String savePath = url + "/" + path;  //

        try {
            FileUtil.mkdir(savePath);
        } catch (Exception e) {
            log.error("创建文件夹失败");
            throw new RuntimeException(e);
        }

        for (MultipartFile multipartFile : file) {

            if (multipartFile.getSize() == 0) {
                log.info("文件为空");
                continue;
            }

            String fileName = System.currentTimeMillis() + "_" + Objects.requireNonNull(multipartFile
                .getOriginalFilename()).replaceAll(" ", "");

            String filePath = savePath + fileName.replaceAll(" ", "_");

            copyFile(multipartFile, filePath);

            OssFile ossFile = ossFactory.createOssFile(Subject, Integer.valueOf(user), multipartFile, filePath);

            ossFile.setClientUrl(clientUrl + path + fileName);
            try {
                ossFile.setFileHash(getMD5Checksum(filePath));
            } catch (IOException | NoSuchAlgorithmException e) {
                throw new RuntimeException(e);
            }

            ossFiles.add(ossFile);

            Boolean save = save(ossFile);

            if (!save) {
                log.info("保存失败");
            }

        }

        return ossFiles;
    }

    public List<OssFile> saveFile(String Subject, String user, File... file) {

        List<OssFile> ossFiles = new ArrayList<>();

        String path = user + "/" + Subject + "/";
        String savePath = url + "/" + path;

        try {
            FileUtil.mkdir(savePath);
        } catch (Exception e) {
            log.error("创建文件夹失败");
            throw new RuntimeException(e);
        }

        File type;
        for (File multipartFile : file) {

            String fileName = System.currentTimeMillis() + "_" + multipartFile.getName().replaceAll(" ", "");

            String filePath = savePath + fileName.replaceAll(" ", "_");

            type = FileUtil.copyFile(multipartFile, new File(filePath));

            OssFile ossFile = ossFactory.createOssFile(Subject, Integer.valueOf(user), type, filePath);

            ossFile.setClientUrl(clientUrl + path + fileName);
            try {
                ossFile.setFileHash(getMD5Checksum(filePath));
            } catch (IOException | NoSuchAlgorithmException e) {
                throw new RuntimeException(e);
            }

            ossFiles.add(ossFile);

            Boolean save = save(ossFile);

            if (!save) {
                log.info("保存失败");
            }

        }

        return ossFiles;
    }

    /**
     * 保存默认生成头像
     * 
     * @param Subject 主题
     * @param user    用户
     * @param bytes   头像流
     */
    public List<OssFile> saveFile(String Subject, String user, byte[]... bytes) {

        String path = user + "/" + Subject + "/";
        String savePath = url + "/" + path;
        List<OssFile> ossFiles = new ArrayList<>();
        for (byte[] flux : bytes) {
            OutputStream outputStream;
            String fileName = user + "_" + System.currentTimeMillis() + ".png";
            try {

                File mkdir = FileUtil.mkdir(savePath);

                outputStream = Files.newOutputStream(Paths.get(savePath + fileName));
                outputStream.write(flux);
                outputStream.flush();
                outputStream.close();

                OssFile ossFile = ossFactory.createOssFile(Subject, Integer
                    .valueOf(user), new File(savePath + fileName), savePath + fileName);

                ossFile.setFileHash(getMD5Checksum(savePath + fileName));
                ossFile.setClientUrl(clientUrl + path + fileName);

                ossFiles.add(ossFile);

                Boolean save = save(ossFile);

                if (!save) {
                    log.info("保存失败");
                }
            } catch (IOException | NoSuchAlgorithmException e) {
                throw new RuntimeException(e);
            }

        }

        return ossFiles;
    }

    public void deleteFile(String path) {
        log.info("删除文件:{}", path);
        try {
            File file = new File(path);
            if (file.exists()) {
                boolean delete = file.delete();
                if (!delete) {
                    log.error("文件删除失败");
                }
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private void copyFile(MultipartFile file, String path) {
        OutputStream outputStream;
        try {

            InputStream inputStream = file.getInputStream();
            outputStream = Files.newOutputStream(Paths.get(path));

            outputStream.write(inputStream.readAllBytes());

            outputStream.flush();
            outputStream.close();
            inputStream.close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

    }

    private void copyFile(File file, String path) {
        try (InputStream inputStream = Files.newInputStream(file.toPath());
            OutputStream outputStream = new FileOutputStream(path, true)) {

            outputStream.write(inputStream.readAllBytes());

            outputStream.flush();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private Boolean save(OssFile ossFile) {
        return ossFileService.save(ossFile);
    }

    @Transactional(rollbackFor = Exception.class)
    public int deleteFile(Integer ossFileId) {

        OssFile byId = ossFileService.getById(ossFileId);
        deleteFile(byId.getServerPath());
        ossFileService.removeById(ossFileId);

        return 0;
    }

    @Transactional(rollbackFor = Exception.class)
    public void deleteFiles(Integer[] ossFileIds) {

        for (Integer i : ossFileIds) {
            int i1 = deleteFile(i);
            if (i1 != 0) {
                return;
            }
        }

    }

    @Transactional(rollbackFor = Exception.class)
    public void deleteFiles(List<String> ossFileIds) {

        for (String i : ossFileIds) {
            log.info("删除文件:{}", i);
            if (i.isEmpty()) {
                continue;
            }

            if (i.equals(" ")) {
                continue;
            }

            int i1 = deleteFile(Integer.parseInt(i));
            if (i1 != 0) {
                return;
            }
        }

    }

    /**
     * 保存大文件
     * 
     * @param chunk      分片文件
     * @param user       用户
     * @param hash       文件的哈希值
     * @param chunkHash  分片文件的哈希值
     * @param chunkIndex 分片文件的索引
     *
     * @return Integer 1 分片文件保存成功 0 分片文件保存失败
     **/
    public Integer saveBigFile(MultipartFile chunk, String user, String hash, String chunkIndex, String chunkHash) {

        String path = user + "/temp/" + hash + "/" + hash + "-" + chunkIndex;
        String savePath = url + "/" + path;

        FileUtil.mkdir(url + "/" + user + "/temp/" + hash + "/");

        copyFile(chunk, savePath);

        String md5Checksum;

        try {
            md5Checksum = getMD5Checksum(savePath);
        } catch (IOException | NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        }

        //        if (md5Checksum.equals(chunkHash)) {
        //            saveFileOk(hash, user);
        //            // 分片文件保存成功
        //            log.info("ok");
        //            return 1;
        //        } else {
        //            // 分片文件保存失败
        //            deleteFile(savePath);
        //            return 0;
        //        }
        saveFileOk(hash, user);
        // 分片文件保存成功
        log.info("ok");
        return 1;
    }

    /**
     * 合并大文件
     * 
     * @param user    用户
     * @param hash    文件的哈希值
     * @param subject 文件主题
     * @return OssFile 文件信息
     */
    public OssFile saveBigFile(String user, String hash, String subject, String fileName) {

        String path = user + "/temp/" + hash + "/";
        String filePath = url + "/" + path + hash + "-";
        int fileIndex = 0;
        File file = new File(url + "/" + path);
        File[] files = file.listFiles();

        if (files != null) {

            OutputStream outputStream;

            String fileUrl = user + "/" + subject + "/" + System.currentTimeMillis() + "_" + fileName;
            String savePath = url + "/" + fileUrl;
            FileUtil.mkdir(url + "/" + user + "/" + subject + "/");

            try {
                outputStream = new FileOutputStream(savePath);
            } catch (FileNotFoundException e) {
                throw new RuntimeException(e);
            }

            for (; fileIndex < files.length; fileIndex++) {

                try (FileInputStream inputStream = new FileInputStream(filePath + fileIndex)) {

                    outputStream.write(inputStream.readAllBytes());
                    outputStream.flush();

                } catch (IOException e) {
                    throw new RuntimeException(e);
                }

            }

            try {
                outputStream.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }

            OssFile ossFile = ossFactory.createOssFile(subject, Integer.parseInt(user), new File(savePath), savePath);

            try {
                ossFile.setFileHash(getMD5Checksum(savePath));
            } catch (IOException | NoSuchAlgorithmException e) {
                throw new RuntimeException(e);
            }

            ossFile.setClientUrl(clientUrl + fileUrl);

            save(ossFile);

            deleteFileStatus(hash, user);

            return ossFile;
        }

        return null;
    }

    private void saveFileOk(String hash, String userId) {

        FileStatus fileStatus = fileStatusRedis.get(hash + "_" + userId);

        if (fileStatus == null) {
            fileStatus = new FileStatus();
            fileStatus.setCurrentIndex(0);
            fileStatus.setNextIndex(1);
            fileStatus.setHash(hash);
            fileStatusRedis.set(hash + "_" + userId, fileStatus);
        }

        fileStatus.setCurrentIndex(fileStatus.getCurrentIndex() + 1);
        fileStatus.setNextIndex(fileStatus.getNextIndex() + 1);
        fileStatusRedis.set(hash + "_" + userId, fileStatus);
    }

    private void deleteFileStatus(String hash, String userId) {
        fileStatusRedis.delete(hash + "_" + userId);
    }

    public FileStatus getFileStatus(String hash, Integer userId) {
        return fileStatusRedis.get(hash + "_" + userId);
    }

    /**
     * 计算文件的MD5校验码
     *
     * @param filePath 文件路径
     * @return MD5校验码
     * @throws IOException              如果发生I/O错误
     * @throws NoSuchAlgorithmException 如果找不到MD5算法
     */
    public static String getMD5Checksum(String filePath) throws IOException, NoSuchAlgorithmException {
        BigInteger bi = null;
        try {
            byte[] buffer = new byte[8192];
            int len = 0;
            MessageDigest md = MessageDigest.getInstance("MD5");
            File f = new File(filePath);
            FileInputStream fis = new FileInputStream(f);
            while ((len = fis.read(buffer)) != -1) {
                md.update(buffer, 0, len);
            }
            fis.close();
            byte[] b = md.digest();
            bi = new BigInteger(1, b);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return bi.toString(16);
    }

}
